/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.kk.app.render;

import android.graphics.SurfaceTexture;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Surface;

import com.kk.app.RenderData;

import java.util.Collection;

import javax.microedition.khronos.egl.EGLContext;

/**
 * GLThreadManager handles the thread used for rendering into the configured output surfaces.
 */
public class GLThreadManager {
    private final String TAG;
    private static final boolean DEBUG = false;

    private static final int MSG_NEW_CONFIGURATION = 1;
    private static final int MSG_NEW_FRAME = 2;
    private static final int MSG_CLEANUP = 3;
    private static final int MSG_DROP_FRAMES = 4;
    private static final int MSG_ALLOW_FRAMES = 5;
    private static final int MSG_ADD_SURFACE = 6;
    private static final int MSG_DEL_SURFACE = 7;
    private static final int MSG_UPDATE_TEXTURE_SIZE_PRE = 8;
    private static final int MSG_PLAY_PLAY = 9;
    private static final int MSG_PLAY_STOP = 10;
    private static final int MSG_UPDATE_TEXTURE_SIZE_RENDER = 11;


    private final SurfaceTextureRenderer mTextureRenderer;

    private final RequestHandlerThread mGLHandlerThread;

    private final FpsCounter mPrevCounter =
            new FpsCounter("GL Preview Producer");
    private volatile boolean mInitSucceed = false;

    /**
     * Container object for Configure messages.
     */
    private static class ConfigureHolder {
        public final ConditionVariable condition;
        public final Surface surfaces;
        public final int w;
        public final int h;
        public final long key;

        public ConfigureHolder(ConditionVariable condition, Surface surface, int w_, int h_, long key_) {
            this.condition = condition;
            this.surfaces = surface;
            this.w = w_;
            this.h = h_;
            this.key = key_;
        }
    }

    private final Handler.Callback mGLHandlerCb = new Handler.Callback() {
        private boolean mCleanup = false;
        private boolean mConfigured = false;
        private boolean mDroppingFrames = false;

        @SuppressWarnings("unchecked")
        @Override
        public boolean handleMessage(Message msg) {
            if (mCleanup) {
                return true;
            }
            try {
                switch (msg.what) {
                    case MSG_NEW_CONFIGURATION:
                        ConfigureHolder configure = (ConfigureHolder) msg.obj;
                        mTextureRenderer.cleanupEGLContext();
                        mTextureRenderer.configureSurfaces(configure.surfaces, configure.w, configure.h, configure.key);
                        configure.condition.open();
                        mConfigured = true;
                        break;
                    case MSG_NEW_FRAME:
                        if (mDroppingFrames) {
                            Log.w(TAG, "Ignoring frame.");
                            break;
                        }
                        if (DEBUG) {
                            mPrevCounter.countAndLog();
                        }
                        if (!mConfigured) {
                            Log.e(TAG, "Dropping frame, EGL context not configured!");
                        }
                        mTextureRenderer.drawIntoSurfaces(null);
                        break;
                    case MSG_ADD_SURFACE: {
                        SurfaceMsgData data = (SurfaceMsgData) msg.obj;
                        if (data != null) {
                            mTextureRenderer.addSurface(data.surface, data.sw, data.sh, data.key, data.surfaceType, data.maxScreen);
                        }
                    }
                    break;
                    case MSG_DEL_SURFACE: {
                        SurfaceMsgData data = (SurfaceMsgData) msg.obj;
                        if (data != null) {
                            mTextureRenderer.delSurface(data.key);
                        }
                    }
                    break;
                    case MSG_CLEANUP:
                        mTextureRenderer.cleanupEGLContext();
                        mCleanup = true;
                        mConfigured = false;
                        break;
                    case MSG_DROP_FRAMES:
                        mDroppingFrames = true;
                        break;
                    case MSG_ALLOW_FRAMES:
                        mDroppingFrames = false;
                        break;
                    case MSG_UPDATE_TEXTURE_SIZE_PRE:
                        if (mTextureRenderer != null) {
                            VideoInfo data = (VideoInfo) msg.obj;
                            if (data != null) {
                                mTextureRenderer.updateTextureSizePrepare(data.videoW, data.videoH, data.videoDecoderW, data.videoDecoderH);
                            }
                        }
                        break;
                    case MSG_UPDATE_TEXTURE_SIZE_RENDER:
                        if (mTextureRenderer != null) {
                            VideoInfo data = (VideoInfo) msg.obj;
                            if (data != null) {
                                mTextureRenderer.updateTextureSizeRenderStart(data.videoW, data.videoH, data.videoDecoderW, data.videoDecoderH);
                            }
                        }
                        break;
                    case RequestHandlerThread.MSG_POKE_IDLE_HANDLER:
                        // OK: Ignore message.
                        break;
                    case MSG_PLAY_PLAY:
                        if (mTextureRenderer != null) {
                            mTextureRenderer.setDrawVideo(true);
                        }
                        break;
                    case MSG_PLAY_STOP:
                        if (mTextureRenderer != null) {
                            mTextureRenderer.setDrawVideo(false);
                        }
                        break;
                    default:
                        Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
                        break;
                }
            } catch (Exception e) {
                Log.e(TAG, "Received exception on GL render thread: ", e);
            }
            return true;
        }
    };

    /**
     * Create a new GL thread and renderer.
     */
//    public GLThreadManager(int facing) {
//        this(facing, null);
//    }
    public GLThreadManager(int facing, EGLContext sharedEglContext, MVRenderData mvRenderData) {
        mTextureRenderer = new SurfaceTextureRenderer(facing, mvRenderData);
        mTextureRenderer.setSharedEglContext(sharedEglContext);
        TAG = String.format("GLThreadManager ssplayer-%d", facing);
        mGLHandlerThread = new RequestHandlerThread(TAG, mGLHandlerCb);

    }

    /**
     * Start the thread.
     *
     * <p>
     * This must be called before queueing new frames.
     * </p>
     */
    public void start() {
        mGLHandlerThread.start();
    }

    /**
     * Wait until the thread has started.
     */
    public void waitUntilStarted() {
        mGLHandlerThread.waitUntilStarted();
        Handler handler = mGLHandlerThread.getHandler();
        if (handler != null) {
            handler.post(new Runnable() {
                @Override
                public void run() {

                    try {
                        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    Log.i(TAG, "run: 设置优先级");
                }
            });
        }
    }

    /**
     * Quit the thread.
     *
     * <p>
     * No further methods can be called after this.
     * </p>
     */
    public void quit() {
        Handler handler = mGLHandlerThread.getHandler();
        handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
        mGLHandlerThread.quitSafely();
        try {
            mGLHandlerThread.join();
        } catch (InterruptedException e) {
            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
                    mGLHandlerThread.getName(), mGLHandlerThread.getId()));
        }
    }

    /**
     * Queue a new call to draw into the surfaces specified in the next available preview
     */
    public void queueNewFrame(boolean force) {
        Handler handler = mGLHandlerThread.getHandler();

        /**
         * Avoid queuing more than one new frame.  If we are not consuming faster than frames
         * are produced, drop frames rather than allowing the queue to back up.
         */
        if (!handler.hasMessages(MSG_NEW_FRAME) || force) {
            handler.sendMessage(handler.obtainMessage(MSG_NEW_FRAME));
        } else {
            Log.e(TAG, "GLThread dropping frame.  Not consuming frames quickly enough!");
        }
    }

    /**
     * Configure the GL renderer for the given set of output surfaces, and block until
     * this configuration has been applied.
     */
    public void setConfigurationAndWait(Surface s, int sw, int sh, long key) {
        Handler handler = mGLHandlerThread.getHandler();

        final ConditionVariable condition = new ConditionVariable(/*closed*/false);
        ConfigureHolder configure = new ConfigureHolder(condition, s, sw, sh, key);

        Message m = handler.obtainMessage(MSG_NEW_CONFIGURATION, /*arg1*/0, /*arg2*/0, configure);
        handler.sendMessage(m);

        // Block until configuration applied.
        condition.block();
    }


    private class VideoInfo {
        VideoInfo(int vw, int vh, int vdw, int vdh) {
            videoW = vw;
            videoH = vh;
            videoDecoderW = vdw;
            videoDecoderH = vdh;
        }

        int videoW;
        int videoH;
        int videoDecoderW;
        int videoDecoderH;
    }

    private class SurfaceMsgData {
        public final Surface surface;
        public final int sw;
        public final int sh;
        public final long key;
        //4和1镜像的时候使用
        public int surfaceType = 0;
        public int maxScreen = 4;

        public SurfaceMsgData(Surface s_, int w_, int h_, long key_) {
            this.surface = s_;
            this.sw = w_;
            this.sh = h_;
            this.key = key_;
            this.surfaceType = 0;
            this.maxScreen = 4;
        }

        public SurfaceMsgData(Surface s_, int w_, int h_, long key_, int sType_, int maxScreen_) {
            this.surface = s_;
            this.sw = w_;
            this.sh = h_;
            this.key = key_;
            this.surfaceType = sType_;
            this.maxScreen = maxScreen_;
        }
    }

    public void addSurface(Surface surface, int w, int h, long key) {

        synchronized (this) {
            if (!mInitSucceed) {
                mInitSucceed = true;
                this.setConfigurationAndWait(surface, w, h, key);
                this.getCurrentSurfaceTexture().setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
                    @Override
                    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                        queueNewFrame(false);
                    }
                });
                RenderData.getMediaPlayer().setSurface(new Surface(this.getCurrentSurfaceTexture()));

            }
        }

        Log.i(TAG, "addSurface: w:" + w + " h:" + h);
        Handler handler = mGLHandlerThread.getHandler();
        Message m = handler.obtainMessage(MSG_ADD_SURFACE, 0, 0, new SurfaceMsgData(surface, w, h, key));
        handler.sendMessage(m);

        queueNewFrame(true);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                queueNewFrame(true);
                Log.i(TAG, "run: 强制延迟刷新执行");
            }
        }, 300);
    }
    

    public void delSurface(long key) {
        Log.i(TAG, "delSurface:");
        Handler handler = mGLHandlerThread.getHandler();
        Message m = handler.obtainMessage(MSG_DEL_SURFACE, new SurfaceMsgData(null, 0, 0, key));
        handler.sendMessage(m);
    }

    public void notifyTextureSizePre(int sw, int sh, int dw, int dh) {
        if (sw <= 0 || sh <= 0) {
            //Log.e(TAG, "updateTextureSize: 视频大小不合法 w:"+sw +" sh:"+sh);
            return;
        }
        Log.i(TAG, "updateTextureSize:");
        Handler handler = mGLHandlerThread.getHandler();
        Message m = handler.obtainMessage(MSG_UPDATE_TEXTURE_SIZE_PRE, 0, 0, new VideoInfo(sw, sh, dw, dh));
        handler.sendMessage(m);
    }

    public void notifyTextureSizeRenderStart(int sw, int sh, int dw, int dh) {
        if (sw <= 0 || sh <= 0) {
            //Log.e(TAG, "updateTextureSize: 视频大小不合法 w:"+sw +" sh:"+sh);
            return;
        }
        Log.i(TAG, "updateTextureSize:");
        Handler handler = mGLHandlerThread.getHandler();
        Message m = handler.obtainMessage(MSG_UPDATE_TEXTURE_SIZE_RENDER, 0, 0, new VideoInfo(sw, sh, dw, dh));
        handler.sendMessage(m);
    }

    /**
     * Get the underlying surface to produce frames from.
     *
     * <p>
     * This returns the surface that is drawn into the set of surfaces passed in for each frame.
     * This method should only be called after a call to
     * {@link #setConfigurationAndWait(Collection)}.  Calling this before the first call
     * to {@link #setConfigurationAndWait(Collection)}, after {@link #quit()}, or
     * concurrently to one of these calls may result in an invalid
     * {@link SurfaceTexture} being returned.
     * </p>
     * @return an {@link SurfaceTexture} to draw to.
     */
    public SurfaceTexture getCurrentSurfaceTexture() {
        return mTextureRenderer.getSurfaceTexture();
    }

    /**
     * Ignore any subsequent calls to
     */
    public void ignoreNewFrames() {
        mGLHandlerThread.getHandler().sendEmptyMessage(MSG_DROP_FRAMES);
    }

    /**
     * Wait until no messages are queued.
     */
    public void waitUntilIdle() {
        mGLHandlerThread.waitUntilIdle();
    }

    /**
     * Re-enable drawing new frames after a call to {@link #ignoreNewFrames()}.
     */
    public void allowNewFrames() {
        mGLHandlerThread.getHandler().sendEmptyMessage(MSG_ALLOW_FRAMES);
    }
}
